

namespace Stee.CAP8.MessageBus.Kafka.Models
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using NLog;
    using Stee.CAP8.MessageBus.Interfaces.Delegates;
    using Stee.CAP8.MessageBus.Interfaces.Enums;
    using Stee.CAP8.MessageBus.Interfaces.Models;

    /// <summary>
    /// Handler for event message type
    /// </summary>
    internal sealed class HandlerEventDetails : HandlerDetails<EventMessage>
    {
        private readonly static Logger Logger = LogManager.GetCurrentClassLogger();


        /// <summary>
        /// Array length to store synchronous & asynchronous handlers
        /// </summary>
        private const int EventHandlerArrayLength = 2;


        #region Fields

        /// <summary>
        /// Overridden logger
        /// </summary>
        protected override Logger NLogger => Logger;

        /// <summary>
        /// Priority level to synchronous & asynchronous message handler mapping.
        /// Tuple item 1: Synchronous handlers
        /// Tuple item 2: Asynchronous handlers
        /// </summary>
        private readonly Dictionary<PriorityLevel, HandleSubscribe<EventMessage>[]> prioritisedHandlers;

        #endregion Fields


        #region Constructor

        /// <summary>
        /// Initializes a new instance of the HandlerEventDetails class
        /// </summary>
        internal HandlerEventDetails()
        {
            this.prioritisedHandlers = new Dictionary<PriorityLevel, HandleSubscribe<EventMessage>[]>
            {
                { PriorityLevel.Low, new HandleSubscribe<EventMessage>[EventHandlerArrayLength] },
                { PriorityLevel.Normal, new HandleSubscribe<EventMessage>[EventHandlerArrayLength] },
                { PriorityLevel.High, new HandleSubscribe<EventMessage>[EventHandlerArrayLength] },
            };
        }

        #endregion Constructor


        #region Public Methods

        /// <summary>
        /// Add new message handler based on specified priority and asynchronous flag
        /// </summary>
        /// <param name="handler">Message handler</param>
        /// <param name="priority">Priority level</param>
        /// <param name="isAsync">Flag indicating if handler processing is asynchronous or synchronous</param>
        /// <returns>Return TRUE if added successfully else FALSE</returns>
        internal bool AddPrioritisedHandler(HandleSubscribe<EventMessage> handler, PriorityLevel priority, bool isAsync)
        {
            try
            {
                if (handler == null)
                {
                    throw new ArgumentNullException($"Null priority handler encountered. {nameof(handler)}");
                }

                //// If mapping's handler is empty, it is null. Being null, it is still safe to use (+) operator 
                if (isAsync)
                {
                    this.prioritisedHandlers[priority][1] += handler;
                }
                else
                {
                    this.prioritisedHandlers[priority][0] += handler;
                }

                return true;
            }
            catch (Exception ex)
            {
                NLogger.Error(ex, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Remove existing message handler based on specified priority and asynchronous flag
        /// </summary>
        /// <param name="handler">Message handler</param>
        /// <param name="priority">Priority level</param>
        /// <param name="isAsync">Flag indicating if handler processing is asynchronous or synchronous</param>
        /// <returns>Return TRUE if removed successfully else FALSE</returns>
        internal bool RemovePrioritisedHandler(HandleSubscribe<EventMessage> handler, PriorityLevel priority, bool isAsync)
        {
            try
            {
                if (handler == null)
                {
                    throw new ArgumentNullException($"Null priority handler encountered. {nameof(handler)}");
                }

                //// If mapping's handler is empty, it is null. Being null, it is still safe to use (-) operator 
                if (isAsync)
                {
                    this.prioritisedHandlers[priority][1] -= handler;
                }
                else
                {
                    this.prioritisedHandlers[priority][0] -= handler;
                }

                return true;
            }
            catch (Exception ex)
            {
                NLogger.Error(ex, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Get existing message handler based on specified priority and asynchronous flag
        /// </summary>
        /// <param name="priority">Priority level</param>
        /// <param name="isAsync">Flag indicating if handler processing is asynchronous or synchronous</param>
        /// <returns>Return prioritised message handler</returns>
        internal HandleSubscribe<EventMessage> GetPrioritisedHandler(PriorityLevel priority, bool isAsync)
        {
            try
            {
                return isAsync
                    ? this.prioritisedHandlers[priority][1]
                    : this.prioritisedHandlers[priority][0];
            }
            catch (Exception ex)
            {
                NLogger.Error(ex, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Invoke message handling
        /// </summary>
        /// <param name="topicName">Message topic name</param>
        /// <param name="message">Event message</param>
        /// <param name="publishUtcDT">Message published date time in UTC</param>
        /// <returns>Return TRUE if invoked successfully else FALSE</returns>
        internal override bool Invoke(TopicName topicName, EventMessage message, DateTime publishUtcDT)
        {
            try
            {
                if (topicName == null)
                {
                    throw new ArgumentNullException($"Failed to invoke event message handling, parameter \"{nameof(topicName)}\" is null.");
                }
                else if (message == null)
                {
                    throw new ArgumentNullException($"Failed to invoke event message handling, parameter \"{nameof(message)}\" is null.");
                }

                var orderedDict = this.prioritisedHandlers.OrderBy(x => x.Key).ToList();

                foreach (var item in orderedDict)
                {
                    //// Sync
                    var syncHandlers = item.Value[0];
                    syncHandlers?.Invoke(topicName, message, publishUtcDT);

                    //// Async
                    HandlerAsync(item.Value[1], topicName, message, publishUtcDT);
                }

                return true;
            }
            catch (Exception ex)
            {
                NLogger.Error(ex, ex.Message);
                return false;
            }
        }

        #endregion Public Methods


        #region Private Methods
        #endregion Private Methods
    }
}
